<!DOCTYPE html>
<!--
Create two sources and play them simultaneously.  This tests unity-gain summing of AudioNode inputs.
The result should be some laughing playing at the same time as the drumming.
-->
<html>
  <head>
    <title>
      mixing.html
    </title>
    <script src="../resources/testharness.js"></script>
    <script src="../resources/testharnessreport.js"></script>
    <script src="resources/audit-util.js"></script>
    <script src="resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      let sampleRate = 44100.0;
      let lengthInSeconds = 2;

      audit.define('test', (task, should) => {
        // Create offline audio context.
        let context = new OfflineAudioContext(
            2, sampleRate * lengthInSeconds, sampleRate);

        // Load up audio files and test
        Promise
            .all([
              // This file is stereo
              Audit.loadFileFromUrl('resources/hyper-reality/br-jam-loop.wav')
                  .then(response => {
                    return context.decodeAudioData(response);
                  }),
              // This file is mono
              Audit.loadFileFromUrl('resources/hyper-reality/laughter.wav')
                  .then(response => {
                    return context.decodeAudioData(response);
                  }),
            ])
            .then(audioBuffers => {
              // Thresholds are experimentally determined
              return runTest(context, audioBuffers, should, [
                {snrThreshold: Infinity, errorThreshold: 0},
                {snrThreshold: Infinity, errorThreshold: 0}
              ]);
            })
            .then(() => task.done());
      });

      audit.run();

      function runTest(context, bufferList, should, testThresholds) {
        should(bufferList.length, 'Number of decoded files').beEqualTo(2);

        // Create two sources and play them at the same time.
        let source1 = context.createBufferSource();
        let source2 = context.createBufferSource();
        source1.buffer = bufferList[0];
        source2.buffer = bufferList[1];

        source1.connect(context.destination);
        source2.connect(context.destination);
        source1.start(0);
        source2.start(0);

        // Verify the number of channels in each source and the expected result.
        should(
            bufferList[0].numberOfChannels,
            'Number of channels in stereo source')
            .beEqualTo(2);

        should(
            bufferList[1].numberOfChannels, 'Number of channels in mono source')
            .beEqualTo(1);

        return context.startRendering().then(audioBuffer => {
          verifyResult(
              audioBuffer, context, bufferList, testThresholds, should);
        });
      }

      function verifyResult(
          renderedBuffer, context, bufferList, testThresholds, should) {
        // Test only works if we have a stereo result.
        should(
            renderedBuffer.numberOfChannels,
            'Number of channels in rendered output')
            .beEqualTo(2);

        // Note: the source lengths may not match the context length.  Create
        // copies of the sources truncated or zero-filled to the rendering
        // length.

        let stereoSource = new AudioBuffer({
          length: renderedBuffer.length,
          numberOfChannels: 2,
          sampleRate: context.sampleRate
        });
        stereoSource.copyToChannel(bufferList[0].getChannelData(0), 0);
        stereoSource.copyToChannel(bufferList[0].getChannelData(1), 1);

        let monoSource = new AudioBuffer({
          length: renderedBuffer.length,
          numberOfChannels: 1,
          sampleRate: context.sampleRate
        });
        monoSource.copyToChannel(bufferList[1].getChannelData(0), 0);

        // Compute the expected result  buffer0 is stereo and buffer1 is mono.
        // The result should be stereo, with the mono source implicitly upmixed
        // to stereo to produce the expected result.
        let expectedBuffer = new AudioBuffer({
          length: renderedBuffer.length,
          numberOfChannels: 2,
          sampleRate: context.sampleRate
        });

        let monoData = monoSource.getChannelData(0);
        for (let c = 0; c < expectedBuffer.numberOfChannels; ++c) {
          let expectedData = expectedBuffer.getChannelData(c);
          let stereoData = stereoSource.getChannelData(c);
          for (let k = 0; k < expectedBuffer.length; ++k) {
            expectedData[k] = stereoData[k] + monoData[k];
          }
        }

        // Compare the rendered data with the expected data for each channel.
        for (let k = 0; k < renderedBuffer.numberOfChannels; ++k) {
          let actualData = renderedBuffer.getChannelData(k);
          let expectedData = expectedBuffer.getChannelData(k);
          let threshold = testThresholds[k];
          let snr = 10 * Math.log10(computeSNR(actualData, expectedData));

          should(snr, 'SNR for channel ' + k)
              .beGreaterThanOrEqualTo(threshold.snrThreshold);
          should(actualData, 'Rendered audio').beCloseToArray(expectedData, {
            absoluteThreshold: threshold.errorThreshold
          });
        }
      }
    </script>
  </body>
</html>
